package com.example.demo.aspect;

import com.example.demo.annotation.RedisLock;
import com.example.demo.util.RedissonUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;


/**
 * @author MarioZzz
 */
@Slf4j
@Aspect
@Component
public class RedisLockAspect {

    @Resource
    private RedissonUtil redissonUtil;

    /**
     * 环绕通知
     */
    @Around(value = "@annotation(redisLock)", argNames = "joinPoint,redisLock")
    public Object around(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable {
        log.info("线程{}，进入切面", Thread.currentThread().getName());

        //获取锁的名称key
        String lockKey = getRedisName(joinPoint, redisLock.lockName());

        Object result = null;
        try {
            //尝试获取锁的次数
            int tryNum = redisLock.tryNum();
            // 尝试获取锁，等待5秒，自己获得锁后一直不解锁则在指定时间后自动解锁
            while (tryNum > 0) {
                /**
                 * 开始尝试获取锁了，返回true表示当前线程获取到了锁，返回false表示没有获取到锁
                 */
                boolean lock = redissonUtil.tryLock(redisLock.LockType(), lockKey, redisLock.timeUnit(), redisLock.waitTime(), redisLock.leaseTime());
                if (lock) {
                    log.info("线程：{}，获取到了锁，开始处理业务", Thread.currentThread().getName());
                    //执行业务逻辑
                    result = joinPoint.proceed();
                    //代码运行到这，业务做完，需要释放锁了
                    redissonUtil.unlock(lockKey);
                    log.info("线程：{}，业务代码处理完毕，锁已释放", Thread.currentThread().getName());
                    break;
                }
                log.info("XXXXX - 线程：{}，没有获取到锁，开始自旋", Thread.currentThread().getName());
                /**
                 * 睡眠500毫秒，休息一小会，给其他拿到锁的线程一点时间去处理业务逻辑代码，再自旋
                 */
                Thread.sleep(500);
                tryNum--;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 获取Redis名称
     *
     * @param joinPoint 切点
     * @param lockName  锁名称
     * @return redisKey
     */
    private String getRedisName(ProceedingJoinPoint joinPoint, String lockName) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method targetMethod = methodSignature.getMethod();
        return lockName + targetMethod.getName();
    }
}
